import util.file as file
import sys
import json
import csv
import os

import urllib.request
import zipfile
import urllib.parse
import _thread
import time

'''
实现数据类,功能如下：
将test_data.json文件夹中的相关数据提取出来
（可先用较小的sample.json练习）
可以自己增加方法
'''

# -------------------------------------------------------------------------------------
# ---题目类---
# -------------------------------------------------------------------------------------
class caseItem:
    # -------------------------------------------------------------------------------------
    def __init__(self, caseId, caseType, caseZip):
        self.__caseId = caseId  # 题号

        self.__caseType = caseType  # 题目类型

        self.__caseZip = caseZip  # 题目地址

        self.__userList = {-1: {"userId": -1, "finalScore": 0.0, "finalUpload": 0.0, "finalTime": 0.0, "finalLine": 0}}  # 做过题目的学生集合

        self.__caseContent = None  # 题目内容，string类型，建议用json中的地址下下来保存，详情可看“《数据科学与基础》大作业补充说明.pdf”

        self.__ifNewest = True  # 当前题目状态是否为最新

        self.__aveFinalScore = 0.0  # 学生平均得分

        self.__aveFre = 0.0  # 学生平均最大上传次数

        self.__time = 0.0  # 学生平均用时（用最后一次上传时间戳减去第一次上传时间戳来估计）

        self.__aveCodeLines = 0.0  # 学生平均代码行数，每个学生的代码行数用最后一次提交的代码行数代替？

    # -------------------------------------------------------------------------------------
    def getCaseId(self):
        return self.__caseId

    def getCaseType(self):
        return self.__caseType

    def getCaseZip(self):
        return self.__caseZip

    def getCaseContent(self):
        return self.__caseContent

    def getFinalScore(self):
        if not self.__ifNewest:
            self.update()
        return self.__aveFinalScore

    def getLines(self):
        if not self.__ifNewest:
            self.update()
        return self.__aveCodeLines

    def getUpLoadFre(self):
        if not self.__ifNewest:
            self.update()
        return self.__aveFre

    def getTime(self):
        if not self.__ifNewest:
            self.update()
        return self.__time

    def getUserNum(self):
        return len(self.__userList)

    # -------------------------------------------------------------------------------------
    # ---判断该题目用户列表是否为空---
    def isEmptyUser(self):
        if len(self.__userList) == 1:
            if -1 in self.__userList:
                return True
            else:
                return False
        elif len(self.__userList) == 0:
            return True
        else:  # len(self.__userList) > 1:
            return False

    # -------------------------------------------------------------------------------------
    # ---向题目用户列表添加一个用户---
    def addUser(self, user):
        if user == None:
            return False  # 无效用户记录 添加失败
        self.__ifNewest = False
        if len(self.__userList) == 1 and -1 in self.__userList:  # 题目用户列表为空
            self.__userList = {user["userId"]: user}
        else:
            self.__userList[user["userId"]] = user
        return True  # 添加成功

    # -------------------------------------------------------------------------------------
    # ---根据id删除用户列表一个用户---
    def deleteUser(self, userId):
        self.__ifNewest = False
        if userId in self.__userList.keys():
            del self.__userList[userId]

    # -------------------------------------------------------------------------------------
    # ---计算更新当前题目各项数据---
    def update(self):
        self.__ifNewest = True  # 设置状态为最新
        self.__aveFinalScore = 0.0
        self.__time = 0.0
        self.__aveFre = 0.0
        self.__aveCodeLines = 0.0
        num = self.getUserNum()
        if not self.isEmptyUser():  # 只有当有用户做题才更新
            for userId in self.__userList.keys():  # 遍历计算
                self.__aveFinalScore += self.__userList[userId]["finalScore"]/num
                self.__time += self.__userList[userId]["finalTime"]/num
                self.__aveFre += self.__userList[userId]["finalUpload"]/num
                self.__aveCodeLines += self.__userList[userId]["finalLine"]/num
            return True
        return False

# -------------------------------------------------------------------------------------
# ---数据存取类---
# 每一个条目为一个Case，以字典存储无序
# -------------------------------------------------------------------------------------
class RawData:
    def __init__(self, path, zipSavePath = "dataBase/all-upload"):
        self.__path = path  # 用于对应json文件
        self.__zipSavePath = zipSavePath  # 下载题目存储路径，默认为"dataBase/all-upload"
        self.__caseList = {'caseId': None}  # 用于存储case所有对象
        # 以下变量用于监视下载进度
        self.__uploadSum = 0  # 总上传数
        self.__alDownload = 0  # 已经下载数
        self.__errorSum = 0  # 下载错误数
        self.__ifFinished = False  # 下载是否完成
        # 开始读取json文件
        self.__read()


    # -------------------------------------------------------------------------------------
    # ---读取对应json文件方法---
    def __read(self):
        f = open(self.__path, encoding='utf-8')
        res = f.read()
        data = json.loads(res)  # 加载json数据
        try:
            _thread.start_new_thread(self.__progress, (data,))
        except Exception as ex:
            print("开启进度条失败，文件下载应该还在继续.")
        ifSuccess = False  # 监视是否至少添加一个用户
        for userId in data.keys():  # 遍历所有用户
            for case in data[userId]["cases"]:  # 遍历某用户所有题目
                uploadSet = case["upload_records"]  # 获取提交集合
                if len(uploadSet) != 0:  # 当前学生提交过当前题目才计入
                    theCase = None
                    if case["case_id"] in self.__caseList:
                        theCase = self.__caseList[case["case_id"]]
                    else:
                        theCase = caseItem(case["case_id"], case["case_type"], case["case_zip"])
                        self.__caseList[case["case_id"]] = theCase
                    userFinal = self.__calFinal(userId, case["final_score"], uploadSet)  # 当前学生对当前题目的最终数据
                    ifSuccess = (theCase.addUser(userFinal) or ifSuccess)
        if ifSuccess:
            del self.__caseList["caseId"]  # 删除创建时多余项
            for caseId in self.__caseList.keys():  # 计算所有case各项数据
                 self.__caseList[caseId].update()
        f.close()
        self.__ifFinished = True  # 通知进度条下载完成
        time.sleep(0.5)  # 等待进度条显示所有信息

    # -------------------------------------------------------------------------------------
    # ---将数据存至给定csv文件下---
    # 默认存至"database/storage.csv"
    def save(self, filePath = "database/storage.csv"):
        tableSize = 8  # 创建表格的列数
        f = open(filePath, 'w', encoding='utf-8')
        csvWriter = csv.writer(f, delimiter='\\')  # todo: csv分隔符定义
        csvWriter.writerow([self.__map(i+1, None, True) for i in range(tableSize)])  # 写入表头
        alCaseNum = len(self.__caseList)  # 总共需要写入的case数量
        caseId = 0  # 从0开始遍历表项从而升序写入
        while alCaseNum > 0:
            if str(caseId) in self.__caseList:
                theCase = self.__caseList[str(caseId)]
                #if not theCase.isEmptyUser():
                csvWriter.writerow([self.__map(i+1, theCase, False) for i in range(tableSize)])  # 写入表项
                alCaseNum -= 1
            caseId += 1
        f.close()

    # ---定义表格每一列内容---
    # todo: csv文件数据项精度定义
    def __map(self, lineNo, case, name_option):
        if lineNo == 1:
            if name_option:
                return "题号"
            else:
                return case.getCaseId()
        elif lineNo == 2:
            if name_option:
                return "类型"
            else:
                return case.getCaseType()
        elif lineNo == 3:
            if name_option:
                return "链接"
            else:
                return case.getCaseZip()
        elif lineNo == 4:
            if name_option:
                return "平均分"
            else:
                return format(case.getFinalScore(), '.3f')
        elif lineNo == 5:
            if name_option:
                return "平均上传次数"
            else:
                return format(case.getUpLoadFre(), '.2f')
        elif lineNo == 6:
            if name_option:
                return "平均用时"
            else:
                return format(case.getTime(), '.2f')
        elif lineNo == 7:
            if name_option:
                return "平均代码行数"
            else:
                return format(case.getLines(), '.2f')
        elif lineNo == 8:
            if name_option:
                return "做题学生数"
            else:
                return case.getUserNum()
        else:
            return "error"

    # -------------------------------------------------------------------------------------
    # ---对一个用户某道题最终数据进行计算处理---
    # 包括用户ID，最终得分，该用户做这道题总提交次数，该用户做这道题的平均代码行数，该用户做这道题花费的时间
    def __calFinal(self, userId, finalScore, uploadSet):
        # 最终结果字典 对应caseItem中caseList存储形式
        userFinal = {"userId": 0, "finalScore": 0.0, "finalUpload": 0.0, "finalTime": 0.0, "finalLine": 0.0}
        count = 0  # 记录最终提交次数
        sumLine = 0  # 记录代码总行数
        firstTime = sys.maxsize  # 记录最早提交时间
        lastTime = 0  # 记录最晚提交时间
        # timeList = []
        for aUpload in uploadSet:
            count += 1
            theLine = self.__calLineNum(aUpload["code_url"])  # 当前提交行数
            if theLine == None:  # 下载文件出现异常 舍去这一次提交
                count -= 1
                continue
            else:
                sumLine += theLine
            # timeList.append(aUpload["upload_time"])
            if aUpload["upload_time"] < firstTime:
                firstTime = aUpload["upload_time"]
            if aUpload["upload_time"] > lastTime:
                lastTime = aUpload["upload_time"]
        if count == 0:  # 用户没有有效提交
            return None
        userFinal["userId"] = userId
        userFinal["finalScore"] = finalScore
        userFinal["finalUpload"] = count
        userFinal["finalLine"] = sumLine/count
        userFinal["finalTime"] = self.__calTimeCost(firstTime, lastTime)
        return userFinal

    # -------------------------------------------------------------------------------------
    # ---计算某一次提交代码行数---
    # 下载提交文件后计算其中main.py中行数
    # todo: 如需变换策略直接在这里修改
    def __calLineNum(self, url):
        self.__alDownload += 1
        try:
            lineNum = 0  # 记录总代码行数
            # 下载文件--
            basename = os.path.basename(url)  # 对应提交的zip文件名
            savePath = self.__zipSavePath + '/' + basename  # 组合存储路径
            if not os.path.exists(savePath):
                urllib.request.urlretrieve(url, savePath)  # 下载zip文件
            # 打开zip文件 计算提交文件(main.py)中的行数--
            direZip = zipfile.ZipFile(savePath)  # 打开下载的zip文件
            direZip.extractall(self.__zipSavePath)  # 压缩包中还有一个压缩包 解压到对应路径（默认为dataBase/all-upload）
            targZipPath = self.__zipSavePath + '/' + direZip.namelist()[0]  # 最终压缩包的路径
            targZip = zipfile.ZipFile(targZipPath)  # 打开最终压缩包
            mainFile = targZip.open('main.py')  # 打开提交文件
            uploadContent = mainFile.read().decode('utf-8')  # 读取提交内容
            # 计算提交代码行数--
            preIsN = False  # 记录上一个字符是否为\n
            for ch in uploadContent:
                if ch == '\n' and not preIsN:
                    lineNum += 1
                preIsN = (ch == '\n')
            # 关闭文件
            targZip.close()
            direZip.close()
            mainFile.close()
            os.remove(targZipPath)  # 删除最终压缩包
        except:
            # print("error")
            self.__errorSum += 1
            return None  # 意味着这一次提交将被舍去
        # 返回
        return lineNum

    # -------------------------------------------------------------------------------------
    # ---估计用户某一道题花费时间---
    # todo: 如需变换策略直接在这里修改
    # 花费时间用最后提交时间减去首次提交时间估计
    # def __calTimeCost(self, firstTime, laseTime, timeList)
    def __calTimeCost(self, firstTime, lastTime):
        return lastTime - firstTime

    # -------------------------------------------------------------------------------------
    # ---监视下载进度---
    def __progress(self, data):
        for userId in data.keys():
            for aCase in data[userId]["cases"]:
                for aUpload in aCase["upload_records"]:
                    self.__uploadSum += 1
        diamonds = '|'
        while not self.__ifFinished:
            finishPart = self.__alDownload/self.__uploadSum*100
            prog = "\r[%s%s]%.2f%%" % (diamonds * int(finishPart), " " * (100 - int(finishPart)), finishPart)
            sys.stdout.write(prog)
            sys.stdout.flush()
            #print(self.__alDownload/self.__uploadSum)
            time.sleep(0.5)
        prog = "\r[%s%s]%.2f%%" % (diamonds * 100, " " * (100 - 100), 100)
        sys.stdout.write(prog)
        sys.stdout.flush()
        print()
        print("Download Finished")
        print("Total:", end=' ')
        print(self.__uploadSum)
        print("Succeed:", end=' ')
        print(self.__uploadSum - self.__errorSum)
        print("Error:", end=' ')
        print(self.__errorSum)

# -------------------------------------------------------------------------------------
